package com.easyaop.core.event;

import com.easyaop.api.LoggerFactory;
import com.easyaop.api.advisor.Advisor;
import com.easyaop.api.pointcut.ClassObject;
import com.easyaop.api.pointcut.MethodObject;
import com.easyaop.api.pointcut.Pointcut;
import com.easyaop.core.Inst;
import com.easyaop.core.listener.DebugAgentListener;
import com.easyaop.core.matcher.TypeElementMatcher;
import com.easyaop.core.AbstractWeaver;
import com.easyaop.core.transformer.PointcutTransformer;

import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;
import net.bytebuddy.matcher.ElementMatchers;

import java.lang.instrument.ClassFileTransformer;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;

import static com.easyaop.core.Inst.inst;


public class PointcutEventListener implements EventListener {
    private static final Logger LOGGER = LoggerFactory.getLogger("PointcutEvent");

    @Override
    public void onEvent(Event event) {

        PointcutEvent aopEvent = (PointcutEvent) event;

        weaving(aopEvent.getChanges());


    }

    private static ConcurrentHashMap<Pointcut, ConcurrentHashMap<Class, Boolean>> cache = new ConcurrentHashMap<>();

    private static boolean filter(Class<?> clz) {
        TypeDescription typeDescription = TypeDescription.ForLoadedType.of(clz);

        return ElementMatchers.not(
                getDefaultIgnore()
                        .or(ElementMatchers.isSynthetic())
                        /*.or((ElementMatcher<TypeDescription>) target -> {
                            try {
                                return target.isAnonymousType();
                            } catch (Throwable ex) {
                                LOGGER.log(Level.WARNING,
                                        "Class: " + clz + ", msg: " + ex.getClass().getName() + " - " + ex.getMessage(),
                                        ex);
                                // return target.getName().matches(".*(?<!\\$)\\$\\d+");
                                return false;
                            }
                        })*/
        ).matches(typeDescription);
    }

    private void weaving(Set<Advisor> advisors) {
        Class[] allLoadedClasses = Inst. inst.getAllLoadedClasses();
        Set<Class> classes = new HashSet<>();
        Arrays.stream(allLoadedClasses)
                .filter(PointcutEventListener::filter)
                .forEach(clz -> {
                    for (Advisor advisor : advisors) {
                        if (matchPointcut(advisor.getPointcut(), clz)) {
                            classes.add(clz);
                            break;
                        }
                        //            }catch (Exception e){
                        //                e.printStackTrace();
                        //            }
                    }
                });

        try {
            if (classes.isEmpty()) {
                return;
            }
            LOGGER.info("weaving " + classes);
            inst.retransformClasses(classes.toArray(new Class[0]));
        } catch (Throwable e) {
            LOGGER.log(Level.WARNING, "" + e, e);
        }

    }

    /**
     * 这个地方缓存暂时无用
     * 只有在同一个切点，进行匹配时，才有用
     */
    private boolean isClass(Pointcut pointcut, Class clz) {
        ConcurrentHashMap<Class, Boolean> classBooleanConcurrentHashMap = cache.computeIfAbsent(pointcut, (key) -> new ConcurrentHashMap<>());

        Boolean b = classBooleanConcurrentHashMap.get(clz);
        if (b != null) {
            return b;
        }

        b = matchPointcut(pointcut, clz);
        classBooleanConcurrentHashMap.put(clz, b);
        return b;
    }

    /**
     * 默认忽视包
     */
    private static ElementMatcher.Junction<? super TypeDescription> getDefaultIgnore() {
        return ElementMatchers.nameStartsWith("java.")
                .or(ElementMatchers.nameStartsWith("jdk."))
                .or(ElementMatchers.nameStartsWith("javax."))
                .or(ElementMatchers.nameStartsWith("sun."))
                .or(ElementMatchers.nameStartsWith("com.sun."))
                .or(ElementMatchers.nameStartsWith("com.easyaop.api"))
                .or(ElementMatchers.nameStartsWith("com.easyaop.core"))
                .or(ElementMatchers.nameStartsWith("com.easyaop.commons"))
                .or(ElementMatchers.nameStartsWith("net.bytebuddy."))
                .or(ElementMatchers.nameStartsWith("org.aspectj."))
                .or(ElementMatchers.isSynthetic());
    }

    private boolean matchPointcut(Pointcut pointcut, Class clz) {
        try {
            if (!pointcut.isClass(new ClassObject.ForClass(clz))) {
                return false;
            }
            Method[] declaredMethods = clz.getDeclaredMethods();
            for (Method declaredMethod : declaredMethods) {
                if (pointcut.isMethod(new MethodObject.ForMethod(declaredMethod))) {
                    return true;
                }
            }
            return false;
        } catch (Throwable e) {
            LOGGER.log(Level.WARNING, "Class: " + clz + ", msg22: " + e.getClass().getName() + " - " + e.getMessage(),
                    e);
            return false;
        }
    }
    static {/*
        ProxyWeaver transformer = new ProxyWeaver();
        ClassFileTransformer buddyTransformer= new AgentBuilder
                .Default()

                .with(new DebugAgentListener())
                .disableClassFormatChanges()
//                .type(matcherType)
                .type(new TypeElementMatcher())
                .transform((builder, typeDescription, classLoader, javaModule, protectionDomain)
//                        -> builder.visit(Advice.to(EventAdvice.class).on(matcherMethod))
                        -> builder.visit(Advice.to(EventAdvice.class).on(new MethodElementMatcher(typeDescription,classLoader)))
                )
                .makeRaw();
        transformer.setTargetTransformer(buddyTransformer);
//        inst.addTransformer(transformer, true);
*/

//        if(true){
//            return;
//        }
        ClassFileTransformer buddyTransformer2 = new AgentBuilder
                .Default()
                .disableClassFormatChanges()
                .ignore(getDefaultIgnore())
                .with(new DebugAgentListener())

                .type(ElementMatchers.not((ElementMatchers.isSynthetic())
                                .or(TypeDescription::isAnonymousType)
                        ).and(new TypeElementMatcher())
                )
                .transform(new PointcutTransformer())
                .makeRaw();
        AbstractWeaver proxyWeaver = new AbstractWeaver();
        proxyWeaver.setTargetTransformer(buddyTransformer2);

        inst.addTransformer(proxyWeaver, true);
        EventSource.EVENT_SOURCE.addEventListener(new PointcutEventListener());

    }
}
